package data

import (
	"context"
	"time"

	pberr "gitee.com/zhangxinzx/notebook/api/notebook/v1"
	"gitee.com/zhangxinzx/notebook/internal/biz"
	"gitee.com/zhangxinzx/notebook/internal/data/ent"
	"gitee.com/zhangxinzx/notebook/internal/data/ent/category"
	"gitee.com/zhangxinzx/notebook/internal/data/ent/note"
	"github.com/go-kratos/kratos/v2/log"
)

const (
	defaultCategoryName = "default"
)

var (
	defaultCategoryID int64 = 0
)

type categoryRepo struct {
	data *Data
	log  *log.Helper
}

func NewCategoryRepo(data *Data, logger log.Logger) biz.CategoryRepo {
	return &categoryRepo{
		data: data,
		log:  log.NewHelper(logger),
	}
}

func (cr *categoryRepo) CreateCategory(ctx context.Context, category *biz.Category) error {
	err := cr.data.db.Category.Create().SetName(category.Name).OnConflict().Ignore().Exec(ctx)
	if err != nil {
		return pberr.ErrorServerInternal("create category [ %s ] failed", category.Name)
	}
	return nil
}

func (cr *categoryRepo) GetCategory(ctx context.Context, id int64) ([]*biz.Note, error) {
	ctg, err := cr.data.db.Category.Query().Where(category.HasNotes(), category.ID(id)).Only(ctx)
	if err != nil && ent.IsNotFound(err) {
		return nil, pberr.ErrorCategoryNotFound("category id [ %d ] is not found", id)
	} else if err != nil {
		return nil, pberr.ErrorServerInternal("get category id [ %d ] failed", id)
	}

	notes, err := ctg.QueryNotes().All(ctx)
	if err != nil {
		return nil, pberr.ErrorServerInternal("get category id [ %d ] failed", id)
	}
	ret := make([]*biz.Note, 0, len(notes))
	for _, note := range notes {
		ret = append(ret, &biz.Note{
			ID:         note.ID,
			CategoryID: id,
			Title:      note.Title,
			CreatedAt:  note.CreatedAt,
			UpdatedAt:  note.UpdatedAt,
		})
	}

	return ret, nil
}

func (cr *categoryRepo) ListCategory(ctx context.Context) ([]*biz.Category, error) {
	ctgs, err := cr.data.db.Category.Query().All(ctx)
	if err != nil {
		return nil, pberr.ErrorServerInternal("get categories failed")
	}

	ret := make([]*biz.Category, 0, len(ctgs))
	for _, ctg := range ctgs {
		ret = append(ret, &biz.Category{
			ID:        ctg.ID,
			Name:      ctg.Name,
			CreatedAt: ctg.CreatedAt,
			UpdatedAt: ctg.UpdatedAt,
		})
	}

	return ret, nil
}

func (cr *categoryRepo) UpdateCategory(ctx context.Context, id int64, cat *biz.Category) error {
	ctg, err := cr.data.db.Category.Query().Where(category.ID(id)).Only(ctx)
	if err != nil && ent.IsNotFound(err) {
		return pberr.ErrorCategoryNotFound("category id [ %d ] is not found", id)
	} else if err != nil {
		return pberr.ErrorServerInternal("get category id [ %d ] failed", id)
	}

	err = ctg.Update().SetName(cat.Name).SetUpdatedAt(time.Now()).Exec(ctx)
	if err != nil {
		return pberr.ErrorServerInternal("get category id [ %d ] failed", id)
	}

	return nil
}

func (cr *categoryRepo) DeleteCategory(ctx context.Context, id int64) error {
	_, err := cr.data.db.Category.Query().Where(category.ID(id)).Only(ctx)
	if err != nil && ent.IsNotFound(err) {
		return pberr.ErrorCategoryNotFound("category id [ %d ] is not found", id)
	} else if err != nil {
		return pberr.ErrorServerInternal("get category id [ %d ] failed", id)
	}

	_, err = cr.data.db.Note.Delete().Where(note.HasCategoriesWith(category.ID(id))).Exec(ctx)
	if err != nil {
		return pberr.ErrorServerInternal("delete category id [ %d ] notes failed", id)
	}

	_, err = cr.data.db.Category.Delete().Where(category.ID(id)).Exec(ctx)
	if err != nil {
		return pberr.ErrorServerInternal("delete category id [ %d ] failed")
	}

	return nil
}
